package net.frenopatico.citadels.modules;

import lombok.extern.java.Log;

import com.google.appengine.api.channel.ChannelService;
import com.google.appengine.api.channel.ChannelServiceFactory;
import com.google.appengine.api.datastore.AsyncDatastoreService;
import com.google.appengine.api.datastore.BaseDatastoreService;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheServiceFactory;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;
import com.google.appengine.api.xmpp.XMPPService;
import com.google.appengine.api.xmpp.XMPPServiceFactory;
import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.inject.AbstractModule;

/**
 * <p> An AppEngine module that sets the stage common GAE services injection
 * (instead of using the GAE static factories). uses Fluent Interface like
 * coding style to build: </p>
 *
 * <pre>
 * Guice.createInjector(AppEngineModule.build().withDatastoreService().withUrlFetchService().withMemcacheService());
 * </pre>
 *
 * <h2>Services injection:</h2>
 *
 * <pre>
 * class Classy {
 *   &#064;Inject
 *   public Classy(Memcache service, DatastoreService data) {
 *     ...
 *   }
 * }
 * </pre>
 *
 * <i>note: You can inject BaseDatastoreService instead of DatastoreService or
 * AsyncDatastoreService</i>
 *
 *
 * @author Yaniv Kessler (yaniv at codeark dot com)
 *
 */
@Log
public final class AppEngineModule extends AbstractModule {
    private LocalServiceTestHelper helper; // NOPMD

    private DatastoreService datastoreService; // NOPMD
    private AsyncDatastoreService asyncDSService; // NOPMD

    private UserService userService; // NOPMD
    private XMPPService xmppService; // NOPMD
    private MemcacheService memcacheService; // NOPMD
    private ChannelService channelService; // NOPMD

    /**
     * Avoid instantiation.
     *
     */
    private AppEngineModule() {
        super();
    }

    @Override
    protected void configure() {
        if( helper != null ) {
            bind( LocalServiceTestHelper.class ).toInstance( helper );
        }

        if( memcacheService != null ) {
            bind( MemcacheService.class ).toInstance( memcacheService );
        }

        if( xmppService != null ) {
            bind( XMPPService.class ).toInstance( xmppService );
        }

        if( userService != null ) {
            bind( UserService.class ).toInstance( userService );
        }

        if( datastoreService != null ) {
            bind( BaseDatastoreService.class ).toInstance( datastoreService );
            bind( DatastoreService.class ).toInstance( datastoreService );
        }

        if( asyncDSService != null ) {
            bind( BaseDatastoreService.class ).toInstance( asyncDSService );
            bind( AsyncDatastoreService.class ).toInstance( asyncDSService );
        }

        if( channelService != null ) {
            bind( ChannelService.class ).toInstance( channelService );
        }

        log.info( String.format( "%s initialized", AppEngineModule.class.getSimpleName() ) );
    }

    /**
     * Returns whether we are running in a Test Environment or not.
     *
     * @return True if in a test environment, false otherwise.
     */
    public boolean isJunitTestEnvironment() {
        return helper != null;
    }

    /**
     * Binds the Memcache Service.
     *
     * @return this module to chain fluent constructors.
     */
    public AppEngineModule withMemcacheService() {
        this.memcacheService = MemcacheServiceFactory.getMemcacheService();
        return this;
    }

    /**
     * BaseDatastoreService binds will be done according to last invocation of
     * this method or withAsyncDatastoreService
     *
     * @return this module to chain fluent constructors.
     */
    public AppEngineModule withDatastoreService() {
        this.datastoreService = DatastoreServiceFactory.getDatastoreService();
        this.asyncDSService = null; // NOPMD
        return this;
    }

    /**
     * BaseDatastoreService binds will be done according to last invocation of
     * this method or withDatastoreService
     *
     * @return this module to chain fluent constructors.
     */
    public AppEngineModule withAsyncDatastoreService() {
        this.datastoreService = null; // NOPMD
        this.asyncDSService = DatastoreServiceFactory.getAsyncDatastoreService();
        return this;
    }

    /**
     * Binds the User Service.
     *
     * @return this module to chain fluent constructors.
     */
    public AppEngineModule withUserService() {
        this.userService = UserServiceFactory.getUserService();
        return this;
    }

    /**
     * Binds the XMPP Service.
     *
     * @return this module to chain fluent constructors.
     */
    public AppEngineModule withXMPPService() {
        this.xmppService = XMPPServiceFactory.getXMPPService();
        return this;
    }

    /**
     * Binds the Channel Service.
     *
     * @return this module to chain fluent constructors.
     */
    public AppEngineModule withChannelService() {
        this.channelService = ChannelServiceFactory.getChannelService();
        return this;
    }

    /**
     * Creates a new instance of the module.
     *
     * @return New instance of the module.
     */
    public static AppEngineModule build() {
        return new AppEngineModule();
    }

    /**
     * Creates a new module for a Test Environment.
     *
     * @return The new module.
     */
    public static AppEngineModule buildWithLocalTestEnvironment() {
        final AppEngineModule aem = new AppEngineModule();
        aem.helper = new LocalServiceTestHelper( new LocalDatastoreServiceTestConfig() );
        aem.helper.setUp();
        return aem;
    }

}
